#include "MySocket.h"
#include "SocketException.h"

using namespace std;

#if defined _WIN32
    int MySocket::m_nb = 0;
#endif   

/**
 * Constructeur, envoie une exception en cas d'échec de création de la socket.
 */
MySocket::MySocket()
{
	#if defined _WIN32
		WSADATA WSAData;
		// Init the sockets DLL.
		if(m_nb<1)
		{
		    WSAStartup(MAKEWORD(2,2), &WSAData);
		}
		m_nb++;
	#endif

	if ((m_socket=socket(AF_INET, SOCK_STREAM, 0))<0){
		throw SocketException("Failed to create socket.", GET_ERROR);
	}
	
	m_cloneNbr = new int;
	*m_cloneNbr = 1;
}

/**
 * Constructeur de socket déja initialisée.
 */
#if defined __unix__
    MySocket::MySocket(int sock)
#elif defined _WIN32
    MySocket::MySocket(SOCKET sock)
#endif
{
	#if defined _WIN32
		m_nb++;
	#endif
    
	m_socket = sock;
	
	m_cloneNbr = new int;
    *m_cloneNbr = 1;
}

/**
 * Constructeur de copie;
 */
MySocket::MySocket(const MySocket& socket)
{
    m_cloneNbr = socket.m_cloneNbr;
    (*m_cloneNbr)++;
    m_socket = socket.m_socket;
}

/**
 * Destructeur.
 */
MySocket::~MySocket()
{
    (*m_cloneNbr)--;
    if((*m_cloneNbr) < 1)
    {
        delete m_cloneNbr;
        #if defined __unix__
            close(m_socket);
        #elif defined _WIN32
            closesocket(m_socket);
            m_nb--;
            if(m_nb<1)
            {
                WSACleanup();
            }
        #endif
    }
}

/**
 * Lit et met dans le buffer buf les len premier caractères depuis la socket.
 * @param buf le buffer
 * @param len le nombre maximum d'octets à lire
 * @param timeOutValue le temps maximum d'écoute de la socket avant abandon, pas de timeout par default, une valeur inférieur a 0 conduit a attendre indéfiniment.
 *
 * @return le nombre d'octets lu ou 0 si aucune donnée n'a pu être lue avant la fin du timeout
 */
int MySocket::read(void *buf, int len, long timeOutValue)
{
	int ret = 0;

	struct timeval timeout;
	fd_set socket;
	
	/* Initialisation du fd_set */
	FD_ZERO(&socket);
	FD_SET(m_socket, &socket);
	
	/* Initialisation du timer */
	timeout.tv_sec=timeOutValue;
	timeout.tv_usec=0;
	
	if((timeOutValue < 0) || select(m_socket, &socket, NULL, NULL, &timeout))
	{	
		ret = recv(m_socket, buf, len, 0);
	}

    if(ret<0)
    {
        throw SocketException("Error while reading message.", GET_ERROR);
    }
	
	return ret;
}

/**
 * Ecrit les len premiers octets du buffer buf dans la socket, envoie une exception si une erreur c'est produite lors de l'ecriture ou si la fin du timeout à été atteinte sans pouvoir écrire
 * @param buf le buffer
 * @param len le nombre d'octets à ecrire
 * @param timeOutValue le temps maximum d'écoute de la socket avant abandon, pas de timeout par default, une valeur inférieur a 0 conduit a attendre indéfiniment.
 * @return le nombre d'octets envoyés
 */
int MySocket::write(const void *buf, int len, long timeOutValue)
{
	struct timeval timeout;
	fd_set socket;
	
	/* Initialisation du fd_set */
	FD_ZERO(&socket);
	FD_SET(m_socket, &socket);
	
	/* Initialisation du timer */
	timeout.tv_sec=timeOutValue;
	timeout.tv_usec=0;
	
	if((timeOutValue < 0) || select(m_socket, NULL, &socket, NULL, &timeout))
	{	
        int byteSend = send(m_socket, buf, len, 0);
        if(byteSend == -1)
		{
			throw SocketException("Error while sending message.", GET_ERROR);
		}
        else
        {
            return byteSend;
        }
	}
	else
	{
		throw SocketException("Socket couldn't be written during timeout delay.", GET_ERROR);
	}
}

/**
 * Assigne la socket à un port, envoie une exception en cas d'échec.
 * @param service le port ou le nom de service à utiliser.
 */
void MySocket::myBind(const char *service)
{
    struct addrinfo hints;
    struct addrinfo *result;
    
    /* Initialisation de l'adresse */
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET;    /* Allow IPv4 or IPv6 */
    hints.ai_socktype = SOCK_STREAM;/* Stream socket */
    hints.ai_flags = AI_PASSIVE;
    hints.ai_protocol = 0;          /* Any protocol */
    hints.ai_canonname = NULL;
    hints.ai_addr = NULL;
    hints.ai_next = NULL;
    
    if(getaddrinfo(NULL, service, &hints, &result)!=0)
    {
        freeaddrinfo(result);
        throw SocketException("Failed to get service info.", GET_ERROR);
    }
	
    if((bind(m_socket, result->ai_addr, (int)result->ai_addrlen))!=0)
    {
        throw SocketException("Failed to bind socket.", GET_ERROR);
    }
}

/**
 * Place la socket en attente de connection, envoie une exception en cas d'échec.
 * @param nbConnectionMax le nombre maximum de client pouvant être mis en attente.
 */
void MySocket::myListen(int nbConnectionMax)
{
     if(listen(m_socket, nbConnectionMax)!=0)
     {
         throw SocketException("Failed to listen socket.", GET_ERROR);
     }
}

/**
 * Essaie de se connecter à un serveur, envoie une exception en cas d'échec.
 * @param serverName le nom ou l'adresse du serveur.
 * @param service le port ou le nom de service à utiliser.
 */
void MySocket::myConnect(const char* serverName, const char *service)
{
    struct addrinfo hints;
    struct addrinfo *result, *rp;
    bool connected = false;
    
    /* Initialisation de l'adresse */
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_INET;    /* Allow IPv4 or IPv6 */
    hints.ai_socktype = SOCK_STREAM;/* Stream socket */
    hints.ai_flags = 0;
    hints.ai_protocol = 0;          /* Any protocol */
    hints.ai_canonname = NULL;
    hints.ai_addr = NULL;
    hints.ai_next = NULL;
    
    if(getaddrinfo(serverName, service, &hints, &result)!=0)
    {
        throw SocketException("Failed to get server info.", GET_ERROR);
    }
    
    rp = result;
    
    while(rp != NULL && !connected)
    {
        if(connect(m_socket, rp->ai_addr, rp->ai_addrlen)==0)
        {
            connected = true;
        }
        rp = rp->ai_next;
    }
    
    if(!connected)
    {
        freeaddrinfo(result);
        throw SocketException("Failed to connect.", GET_ERROR);
    }
    
    freeaddrinfo(result);
}

/**
 * Accepte la connexion d'un client.
 * @param addr sert à renvoyer les informations de connection du client, laisser null pour ne pas utiliser
 * @param addrlen la longueur de l'adresse du client (taille de la struct sockaddr)
 * @param timeOutValue le delai à attendre avant d'abandonner la connection, pas de timeout par default, une valeur inférieur a 0 conduit a attendre indéfiniment.
 * 
 * @return la socket utilisée pour la communication entre le client et le serveur 
 */
MySocket MySocket::myAccept(struct sockaddr *addr, socklen_t *addrlen, long timeOutValue)
{
    struct timeval timeout;
	fd_set socket;
	
	/* Initialisation du fd_set */
	FD_ZERO(&socket);
	FD_SET(m_socket, &socket);
	
	/* Initialisation du timer */
	timeout.tv_sec=timeOutValue;
	timeout.tv_usec=0;
	
	if((timeOutValue < 0) || select(m_socket, &socket, NULL, NULL, &timeout))
	{
	    #if defined __unix__
            int socket;
        #elif defined _WIN32
            SOCKET socket;
        #endif
        socket = accept(m_socket, addr, addrlen);
        if(socket >= 0)
        {
            return MySocket(socket);
        }
        else
        {
            throw SocketException("Failed to connect during acceptation.", GET_ERROR);
        }
	}
	else
	{
	    throw SocketException("Failed to accept connection during timeout delay.", GET_ERROR);
	}
}
